/******************************************************************************
This file is part of AppKit.
Project: appkit
Author : FergusZeng
Email  : cblock@126.com
git	   : https://gitee.com/newgolo/appkit.git
*******************************************************************************
MIT License

Copyright (c) 2022 cblock@126.com

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
******************************************************************************/
#include "appkit/datetime.h"

#include <fcntl.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>

#include <climits>

#include "appkit/strutil.h"
#include "appkit/thread.h"
#include "appkit/tracer.h"

#if defined(OS_CYGWIN)
#define USE_RTC 0
#else
#include <linux/rtc.h>
#define RTC_DEV_PATH "/dev/rtc0"
#define USE_RTC 1
#endif

extern long timezone;  // from time.h
namespace appkit {
Time::Time(int sec, int us) {
    m_sec = sec + us / 1000000;
    m_usec = us % 1000000;
}

Time::Time(uint64 us) {
    m_sec = us / 1000000;
    m_usec = us % 1000000;
}

Time::Time(struct timeval tv) {
    m_sec = tv.tv_sec;
    m_usec = tv.tv_usec;
}

Time::Time(const Time& time) {
    m_sec = time.m_sec;
    m_usec = time.m_usec;
}

Time::~Time() {}

int Time::sec() const { return m_sec; }

int Time::usec() const { return m_usec; }

struct timeval Time::toTimeval() const {
    struct timeval tv {
        m_sec, m_usec
    };
    return tv;
}

std::string Time::toString() {
    return StrUtil::format("%ld.%06d", m_sec, m_usec);
}

uint64 Time::toMicroSec() {
    uint64 microSeconds = m_sec * 1000000LL + m_usec;
    return microSeconds;
}

Time Time::fromEpoch() {
    Time currTime;
    struct timespec currentTs;
    clock_gettime(CLOCK_REALTIME, &currentTs);
    currTime.m_sec = currentTs.tv_sec;
    currTime.m_usec = static_cast<int>(currentTs.tv_nsec / 1000);
    return currTime;
}

Time Time::fromMono() {
    Time currTime;
    struct timespec currentTs;
    clock_gettime(CLOCK_MONOTONIC, &currentTs);
    currTime.m_sec = currentTs.tv_sec;
    currTime.m_usec = static_cast<int>(currentTs.tv_nsec / 1000);
    return currTime;
}

uint64 Time::usSinceMono(const Time& monoStartTime) {
    Time diffTime = Time::fromMono() - monoStartTime;
    return diffTime.toMicroSec();
}

int Time::tzDiffSecs() {
    time_t t = ::time(nullptr);
    struct tm localTm, utcTm;
    localtime_r(&t, &localTm);
    gmtime_r(&t, &utcTm);
    return 3600 * (localTm.tm_hour - utcTm.tm_hour);
}

bool Time::setTime(const Time& utcTime, int tzMinute) {
    struct timeval tv;
    tv.tv_sec = utcTime.sec();
    tv.tv_usec = utcTime.usec();

    struct timezone tzone;
    tzone.tz_minuteswest = tzMinute;
    tzone.tz_dsttime = 0;
    if (settimeofday(&tv, &tzone) != 0) {
        TRACE_E("Time::setTime, set time error: %s !", ERRSTR);
        return false;
    }
    // extern char** environ; //全局环境变量
    std::string tz = "UTC";
    if (tzMinute != 0) {
        tz = StrUtil::format("UTC%+d:00", tzMinute / 60);
    }
    setenv("TZ", tz.data(), 1);
    tzset();
    gettimeofday(&tv, &tzone);
    return true;
}

Time& Time::operator=(const Time& t) {
    m_sec = t.m_sec;
    m_usec = t.m_usec;
    return *this;
}

Time Time::operator+(const Time& time) {
    Time result;
    result.m_sec = m_sec + time.m_sec;
    result.m_usec = m_usec + time.m_usec;
    while (result.m_usec >= 1000000) {
        result.m_sec++;
        result.m_usec -= 1000000;
    }
    return result;
}

Time Time::operator-(const Time& time) {
    Time result;
    result.m_usec = m_usec - time.m_usec;
    if (result.m_usec < 0) {
        result.m_usec += 1000000;
        result.m_sec = m_sec - time.m_sec - 1;
    } else {
        result.m_sec = m_sec - time.m_sec;
    }
    return result;
}

bool Time::operator==(const Time& t) const {
    return ((t.m_sec == this->m_sec) && (t.m_usec == this->m_usec));
}

bool Time::operator<(const Time& t) const {
    if (this->m_sec < t.m_sec) {
        return true;
    } else if (this->m_sec > t.m_sec) {
        return false;
    } else {
        return this->m_usec < t.m_usec;
    }
}

bool Time::operator>(const Time& t) const {
    if (this->m_sec > t.m_sec) {
        return true;
    } else if (this->m_sec < t.m_sec) {
        return false;
    } else {
        return this->m_usec > t.m_usec;
    }
}

DateTime::DateTime() {}

DateTime::DateTime(const Time& epoch) {
    time_t t = epoch.sec();
    struct tm tmTime;
    localtime_r(&t, &tmTime);
    setMSecond(epoch.usec() / 1000);
    setSecond(tmTime.tm_sec);
    setMinute(tmTime.tm_min);
    setHour(tmTime.tm_hour);
    setDay(tmTime.tm_mday);
    setMonth(tmTime.tm_mon + 1);
    setYear(tmTime.tm_year + 1900);
}

DateTime::DateTime(uint32 year, uint32 month, uint32 day, uint32 hour,
                   uint32 minute, uint32 second) {
    setYear(year);
    setMonth(month);
    setDay(day);
    setHour(hour);
    setMinute(minute);
    setSecond(second);
}

DateTime::DateTime(const DateTime& copy) {
    m_year = copy.m_year;
    m_month = copy.m_month;
    m_day = copy.m_day;
    m_hour = copy.m_hour;
    m_minute = copy.m_minute;
    m_second = copy.m_second;
    m_msecond = copy.m_msecond;
}
DateTime::~DateTime() {}
Time DateTime::toEpochTime() {
    struct tm currTm;
    currTm.tm_year = m_year - 1900;
    currTm.tm_mon = m_month - 1;
    currTm.tm_mday = m_day;
    currTm.tm_hour = m_hour;
    currTm.tm_min = m_minute;
    currTm.tm_sec = m_second;
    currTm.tm_isdst = 0;
    time_t seconds = mktime(&currTm);
    if (seconds < 0) {
        seconds = 0;
    }
    return Time(seconds, 0);
}

DateTime DateTime::toUtcTime() {
    auto epoch = toEpochTime();
    time_t t = epoch.sec();
    struct tm tmTime;
    gmtime_r(&t, &tmTime);
    setMSecond(epoch.usec() / 1000);
    setSecond(tmTime.tm_sec);
    setMinute(tmTime.tm_min);
    setHour(tmTime.tm_hour);
    setDay(tmTime.tm_mday);
    setMonth(tmTime.tm_mon + 1);
    setYear(tmTime.tm_year + 1900);
    return *this;
}

uint32 DateTime::weekday() const {
    /* 蔡勒(Zeller)公式:
     * 1582年10月4日或之前: w=y+[y/4]+[c/4]-2c+[13(m+1)/5]+d+2
     * 1582年10月4日之后: w=y+[y/4]+[c/4]-2c+[13(m+1)/5]+d-1
     * y为年份后两位,c为年份前两位,m取值为3~14(1/2月份看作是前一年的13/14月份),d为日期
     * 2020-02-14: 19+4+5-40+39+14-1=40  40%7=5
     * 如果结果是负数,需转换为正数: Week = (w%7+7)%7
     */
    int tmp, y, c, m, d;
    if (m_month <= 2) {
        tmp = m_year - 1;
        m = 12 + m_month;
    } else {
        tmp = m_year;
        m = m_month;
    }
    y = tmp % 100;
    c = tmp / 100;
    d = m_day;
    tmp = m_year * 10000 + m_month * 100 + m_day;
    if (tmp <= 15821004) {
        tmp = y + y / 4 + c / 4 - (c << 1) + (13 * (m + 1) / 5) + d + 2;
    } else {
        tmp = y + y / 4 + c / 4 - (c << 1) + (13 * (m + 1) / 5) + d - 1;
    }
    return (tmp < 0) ? (((tmp % 7) + 7) % 7) : (tmp % 7);
}

int DateTime::timeZone() {
#if 0
    time_t localTime, utcTime;
    struct tm tm_local, tm_utc;
    time(&localTime);
    utcTime = localTime;

    localtime_r(&localTime, &tm_local);
    localTime = mktime(&tm_local);

    gmtime_r(&utcTime, &tm_utc);
    utcTime = mktime(&tm_utc);
    return localTime - utcTime;
#else
    return -timezone;
#endif
}

void DateTime::setTimeZone(const int& tz) {
    char* TZ = getenv("TZ");
    if (TZ) {
        TRACE_D("DateTime::setTimeZone, current TZ=%s", TZ);
    }
    auto offset = tz / 3600;
    std::string data = "TZ=UTC" + std::to_string(offset);
    auto len = data.length();
    TZ = static_cast<char*>(malloc(len + 1));
    data.copy(TZ, len, 0);
    if (putenv(TZ) != 0) {
        TRACE_E("DateTime::setTimeZone, set environment fail!");
    }
}

std::string DateTime::toString() {
    return StrUtil::format("%d-%02d-%02d %02d:%02d:%02d.%03d", m_year, m_month,
                           m_day, m_hour, m_minute, m_second, m_msecond);
}

bool DateTime::isLeapYear() {
    if ((m_year % 4 == 0) && (m_year % 100 != 0)) {
        return true;
    }
    return false;
}

DateTime DateTime::getDateTime() {
    DateTime dateTime;
    time_t nowTime = ::time(nullptr);
    struct tm tmTime;
    localtime_r(&nowTime, &tmTime);
    Time time = Time::fromEpoch();
    dateTime.setMSecond(time.usec() / 1000);
    dateTime.setSecond(tmTime.tm_sec);
    dateTime.setMinute(tmTime.tm_min);
    dateTime.setHour(tmTime.tm_hour);
    dateTime.setDay(tmTime.tm_mday);
    dateTime.setMonth(tmTime.tm_mon + 1);
    dateTime.setYear(tmTime.tm_year + 1900);
    return dateTime;
}

bool DateTime::setDateTime(const DateTime& dateTime) {
    struct tm currentTime;
    currentTime.tm_year = dateTime.year() - 1900;
    currentTime.tm_mon = dateTime.month() - 1;
    currentTime.tm_mday = dateTime.day();
    currentTime.tm_hour = dateTime.hour();
    currentTime.tm_min = dateTime.minute();
    currentTime.tm_sec = dateTime.second();
    currentTime.tm_isdst = 0;
    time_t seconds = mktime(&currentTime);
    if (seconds < 0) {
        TRACE_E("DateTime::setDateTime, make time error: %s !", ERRSTR);
        return false;
    }
    struct timeval tv;
    struct timezone tz;
    if (gettimeofday(&tv, &tz) != 0) {
        TRACE_E("DateTime::setDateTime, get time error: %s !", ERRSTR);
        return false;
    }
    tv.tv_sec = seconds;
    tv.tv_usec = 0;
    if (settimeofday(&tv, nullptr) != 0) {  // tz设置为nullptr,表示使用当前时区
        TRACE_E("DateTime::setDateTime, set time error: %s !", ERRSTR);
        return false;
    }
    return true;
}

DateTime DateTime::getRtcTime() {
    DateTime rtcTime;
#if USE_RTC
    int rtcFd = ::open(RTC_DEV_PATH, O_RDWR | O_CLOEXEC);
    if (rtcFd < 0) {
        TRACE_E("DateTime::getRtcTime, open rtc error:%s.", ERRSTR);
        return getDateTime();
    }
    struct rtc_time rtc_tm;
    int rc = ::ioctl(rtcFd, RTC_RD_TIME, &rtc_tm);
    if (rc < 0) {
        TRACE_E("DateTime::getRtcTime, read rtc error:%s.", ERRSTR);
        close(rtcFd);
        return getDateTime();
    }
    rtcTime.setYear(rtc_tm.tm_year + 1900);
    rtcTime.setMonth(rtc_tm.tm_mon + 1);
    rtcTime.setDay(rtc_tm.tm_mday);
    rtcTime.setHour(rtc_tm.tm_hour);
    rtcTime.setMinute(rtc_tm.tm_min);
    rtcTime.setSecond(rtc_tm.tm_sec);
    close(rtcFd);
    return rtcTime;
#else
    return getDateTime();
#endif
}

bool DateTime::setRtcTime(const DateTime& dateTime) {
#if USE_RTC
    int rtcFd = ::open(RTC_DEV_PATH, O_RDWR | O_CLOEXEC);
    if (rtcFd < 0) {
        TRACE_E("DateTime::setRtcTime, open rtc error:%s.", ERRSTR);
        return false;
    }
    struct rtc_time rtc_tm;
    rtc_tm.tm_year = dateTime.year() - 1900;
    rtc_tm.tm_mon = dateTime.month() - 1;
    rtc_tm.tm_mday = dateTime.day();
    rtc_tm.tm_hour = dateTime.hour();
    rtc_tm.tm_min = dateTime.minute();
    rtc_tm.tm_sec = dateTime.second();
    int rc = ::ioctl(rtcFd, RTC_SET_TIME, &rtc_tm);
    if (rc < 0) {
        TRACE_E("DateTime::setRtcTime, write rtc error:%s.", ERRSTR);
        close(rtcFd);
        return false;
    }
    close(rtcFd);
    return false;
#else
    return setDateTime(dateTime);
#endif
}

bool DateTime::operator==(const DateTime& dt) const {
    return ((dt.m_year == this->m_year) && (dt.m_month == this->m_month) &&
            (dt.m_day == this->m_day) && (dt.m_hour == this->m_hour) &&
            (dt.m_minute == this->m_minute) &&
            (dt.m_second == this->m_second) &&
            (dt.m_msecond == this->m_msecond));
}

DateTime& DateTime::operator=(const DateTime& dt) {
    m_year = dt.m_year;
    m_month = dt.m_month;
    m_day = dt.m_day;
    m_hour = dt.m_hour;
    m_minute = dt.m_minute;
    m_second = dt.m_second;
    m_msecond = dt.m_msecond;
}

Rate::Rate(float frequency) {
    m_start = Time::fromMono();
    m_usInterval = static_cast<uint64>(1000000.0f / fabs(frequency));
    m_usInterval = CLIP(1, m_usInterval, INT_MAX);
}

Rate::Rate(const Rate& rate) {
    m_start = Time::fromMono();
    m_usInterval = rate.m_usInterval;
}

Rate::~Rate() {}

void Rate::reset(float frequency) {
    if (frequency > 0.0f) {
        m_usInterval = static_cast<uint64>(1000000.0f / frequency);
        m_usInterval = CLIP(1, m_usInterval, INT_MAX);
    }
    m_start = Time::fromMono();
}

bool Rate::sleep() {
    auto us = Time::usSinceMono(m_start);
    if (us < m_usInterval) {
        Thread::usleep(m_usInterval - us);
    }
    us = Time::usSinceMono(m_start);
    if (us >= (m_usInterval << 1)) {
        m_start = Time::fromMono();
    } else {
        m_start = m_start + Time(m_usInterval);
    }
    return true;
}

bool Rate::wakeup(float sigma) {
    auto us = Time::usSinceMono(m_start);
    if (us >= m_usInterval) {
        if (us - m_usInterval >= m_usInterval) {
            m_start = Time::fromMono();
        } else {
            m_start = m_start + Time(m_usInterval);
        }
        return true;
    } else if (!F_EQUAL(0.0f, sigma)) {
        auto diff = m_usInterval - us;
        auto toler = static_cast<uint32>(m_usInterval * sigma);
        if (diff <= toler) {
            m_start = m_start + Time(m_usInterval);
            return true;
        }
    }
    return false;
}
}  // namespace appkit
